﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using System.ComponentModel;
using System.Collections.ObjectModel;
using AppToolkit.WPF.ComponentModel;

namespace AppToolkit.WPF.Tests.ComponentModel
{
    [TestFixture]
    public class ChangeTrackerTests
    {
        [Test,ExpectedException(typeof(ArgumentNullException))]
        public void CTOR_throws_if_null_is_passed()
        {
            ChangeTracker target = new ChangeTracker(null);
        }

        [Test]
        public void HasChanges_returns_false_initially()
        {
            ChangeTracker target = new ChangeTracker(new SimpleComponent());
            Assert.IsFalse(target.HasChanges);
        }

        [Test]
        public void HasChanges_returns_true_if_property_changes()
        {
            SimpleComponent testComp = new SimpleComponent();
            ChangeTracker target = new ChangeTracker(testComp);

            testComp.TestProperty = "changed";
            Assert.IsTrue(target.HasChanges);
        }

        #region ObservableCollection Tests
        [Test]
        public void HasChanges_returns_true_if_element_is_added_to_collection()
        {
            CompositeComponent testComp = new CompositeComponent();
            ChangeTracker target = new ChangeTracker(testComp);

            testComp.OCChildren.Add(new SimpleComponent());
            Assert.IsTrue(target.HasChanges);
        }

        [Test]
        public void HasChanges_returns_true_if_element_is_removed_from_collection()
        {
            CompositeComponent testComp = new CompositeComponent();
            testComp.OCChildren.Add(new SimpleComponent());

            ChangeTracker target = new ChangeTracker(testComp);
            testComp.OCChildren.RemoveAt(0);
            Assert.IsTrue(target.HasChanges);
        }

        [Test]
        public void Element_removed_from_collection_is_not_tracked_anymore()
        {
            CompositeComponent testComp = new CompositeComponent();
            SimpleComponent childComp = new SimpleComponent();
            testComp.OCChildren.Add(childComp);

            ChangeTracker target = new ChangeTracker(testComp);
            testComp.OCChildren.RemoveAt(0);
            target.Reset();

            childComp.TestProperty = "changed";
            Assert.IsFalse(target.HasChanges);
        }

        [Test]
        public void HasChanges_returns_true_if_element_in_collection_changes()
        {
            CompositeComponent testComp = new CompositeComponent();
            testComp.OCChildren.Add(new SimpleComponent());

            ChangeTracker target = new ChangeTracker(testComp);
            testComp.OCChildren[0].TestProperty = "changed";
            Assert.IsTrue(target.HasChanges);
        }
        #endregion

        #region BindingList Tests
        [Test]
        public void BindingList_HasChanges_returns_true_if_element_is_added_to_collection()
        {
            CompositeComponent testComp = new CompositeComponent();
            ChangeTracker target = new ChangeTracker(testComp);

            testComp.BLChildren.Add(new SimpleComponent());
            Assert.IsTrue(target.HasChanges);
        }

        [Test]
        public void BindingList_HasChanges_returns_true_if_element_is_removed_from_collection()
        {
            CompositeComponent testComp = new CompositeComponent();
            testComp.BLChildren.Add(new SimpleComponent());

            ChangeTracker target = new ChangeTracker(testComp);
            testComp.BLChildren.RemoveAt(0);
            Assert.IsTrue(target.HasChanges);
        }

        [Test]
        public void BindingList_Element_removed_from_collection_is_not_tracked_anymore()
        {
            CompositeComponent testComp = new CompositeComponent();
            SimpleComponent childComp = new SimpleComponent();
            testComp.BLChildren.Add(childComp);

            ChangeTracker target = new ChangeTracker(testComp);
            testComp.BLChildren.RemoveAt(0);
            target.Reset();

            childComp.TestProperty = "changed";
            Assert.IsFalse(target.HasChanges);
        }

        [Test]
        public void BindingList_HasChanges_returns_true_if_element_in_collection_changes()
        {
            CompositeComponent testComp = new CompositeComponent();
            testComp.BLChildren.Add(new SimpleComponent());

            ChangeTracker target = new ChangeTracker(testComp);
            testComp.BLChildren[0].TestProperty = "changed";
            Assert.IsTrue(target.HasChanges);
        }
        #endregion

        #region Dispose Tests
        [Test]
        public void Dispose_unregisters_all_event_handlers()
        {
            SimpleComponent testComp = new SimpleComponent();
            ChangeTracker target = new ChangeTracker(testComp);

            target.Dispose();

            Assert.IsFalse(testComp.AreHandlersRegistered);
        }
        #endregion

        #region TestComponents
        class SimpleComponent : INotifyPropertyChanged
        {
            #region Property TestProperty
            private string _TestProperty;

            public string TestProperty
            {
                get
                {
                    return _TestProperty;
                }
                set
                {
                    if (_TestProperty != value)
                    {
                        _TestProperty = value;
                        OnPropertyChanged("TestProperty");
                    }
                }
            }
            #endregion

            public bool AreHandlersRegistered
            {
                get
                {
                    return PropertyChanged != null;
                }
            }

            #region INotifyPropertyChanged Members
            protected void OnPropertyChanged(string propertyName)
            {
                if (null != PropertyChanged)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }

            public event PropertyChangedEventHandler PropertyChanged;
            #endregion
        }

        class CompositeComponent : INotifyPropertyChanged
        {
            public CompositeComponent()
            {
                this.OCChildren = new ObservableCollectionEx<SimpleComponent>();
                this.BLChildren = new BindingListEx<SimpleComponent>();
            }

            #region Property TestProperty
            private string _TestProperty;

            public string TestProperty
            {
                get
                {
                    return _TestProperty;
                }
                set
                {
                    if (_TestProperty != value)
                    {
                        _TestProperty = value;
                        OnPropertyChanged("TestProperty");
                    }
                }
            }
            #endregion

            public bool AreHandlersRegistered
            {
                get
                {
                    return this.OCChildren.EventsRegistered > 0 ||
                           this.BLChildren.EventsRegistered > 0;
                }
            }

            public ObservableCollectionEx<SimpleComponent> OCChildren { get; private set; }

            public BindingListEx<SimpleComponent> BLChildren { get; private set; }

            #region INotifyPropertyChanged Members
            protected void OnPropertyChanged(string propertyName)
            {
                if (null != PropertyChanged)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }

            public event PropertyChangedEventHandler PropertyChanged;
            #endregion
        }

        class ObservableCollectionEx<TType> : ObservableCollection<TType>
        {
            public override event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged
            {
                add
                {
                    ++this.EventsRegistered;
                    base.CollectionChanged += value;
                }
                remove
                {
                    base.CollectionChanged -= value;
                    --this.EventsRegistered;
                }
            }

            public int EventsRegistered { get; private set; }
        }

        class BindingListEx<TType> : BindingList<TType>
        {
            public new event AddingNewEventHandler AddingNew
            {
                add
                {
                    ++this.EventsRegistered;
                    base.AddingNew += value;
                }
                remove
                {
                    base.AddingNew -= value;
                    --this.EventsRegistered;
                }
            }

            public new event ListChangedEventHandler ListChanged
            {
                add
                {
                    ++this.EventsRegistered;
                    base.ListChanged += value;
                }
                remove
                {
                    base.ListChanged -= value;
                    --this.EventsRegistered;
                }
            }

            public int EventsRegistered { get; private set; }
        }
        #endregion
    }
}
